#pragma once

#include "bezier.h"
#include "glib/g2/vector.h"
#include "typedefs.h"

namespace outline {

/// What the curve should seek after. It may be either to pass as near to the points as possible or to resemble imaginary polyline made from the points.
enum bezier_build_method {
	pull_to_points_build,
	pull_to_points_free_ends_build,
	pull_to_polyline_build,
	reverse_build,
	arkan_build ///< completely different methods made months after the others
	//simple_implicit_bezier_build, ///< no difference from explicit (pull_to_points_build) on practice
	//newton_bezier_build, ///< behaves badly on practice, to be modified
};

/// The class guesses best 3rd order bezier approximating given polyline (start and finish points are coinside).
/// Also it can use previous guess to faster approximate polyline with additional segments.
template <typename T, typename R = typename type_traits<T>::real_type>
class bezier_builder : public typedefs<T, R>
{
public:
	typedef g2::basic_vector<real_type> vector2;
public:
	bezier_builder();
	bezier_type const & bezier() const
		{ return controls_; }
	///returns minimum number of points in polyline to start building, depends on method
	uint enough_points() const;
	real_type parameter(uint n) const
		{ return parameters_[n]; }
	///builds vector of lengths along the curve for each node in polyline
	void lengths(real_vector_type & l) const;
	///tie is point on bezier curve, those parameter corresponds to n-th point of polyline
	real_point_type tie(uint n) const
		{ return controls_.value(parameters_[n]); }
	void clear();
	bool empty() const
		{ return parameters_.empty(); }
	bezier_type const & build(nodes_type const & polyline);
	/// returns summed square lengths between polyline nodes and built Bezier curve
	real_type chisq() const
		{ return chisq_; }
	/// returns the count of iterations made for the current number of nodes in polyline
	uint total_iterations() const
		{ return total_iterations_; }
	/// total count of points to fit minus 2
	uint inner_nodes() const
		{ return parameters_.size() - 2; }
	/// relative discrepancy per inner node
	real_type mean_chisq() const
		{ return chisq_ / inner_nodes(); }
	bool precision_reached() const
		{ return chisq_ <= inner_nodes() * stop_mean_discrepancy; }
public:
	bezier_build_method method;
	bool auto_refit; ///< if its true then old bezier control points will not be used for polyline with distinct points count
	/// iterations stop criterion
	uint max_iterations; ///< how many iterations build(...) method may do
	real_type stop_mean_discrepancy; ///< sets limit to mean square lengths between polyline nodes and built bezier (chisq() / internal_nodes_count); iterations are stopped as soon as this limit is reached

	real_point_vector_type poles;
	real_point_vector_type samples;
private:
	void init(nodes_type const & polyline);

	bezier_type const & simple_build(nodes_type const & polyline);
	void parameters_to_controls(nodes_type const & polyline);
	void controls_to_parameters_explicit(nodes_type const & polyline);
	//void controls_to_parameters_implicit(nodes_type const & polyline);

	//bezier_type const & newton_build(nodes_type const & polyline);
	//bool newton_iter(nodes_type const & polyline);

	bezier_type const & pull_to_polyline(nodes_type const & polyline);

private: // reverse build
	static bool intersect(real_type & a, vector2 const & p, vector2 const & d, vector2 const & b, vector2 const & n);
	bezier_type const & build_reverse(nodes_type const & polyline);
	bezier_type const & build_arkan(nodes_type const & polyline);

private:
	/// parameters (t) of bezier corresponding to given point last time
	/// parameters.front() == 0, parameters.back() == 1
	real_vector_type parameters_; 
	bezier_type controls_;
	real_type chisq_;
	uint total_iterations_;
};

} //namespace outline
